package com.sjsu.aws.simplockswithoutherdeffect;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.amazonaws.services.dynamodb.model.AttributeValue;
import com.amazonaws.services.dynamodb.model.ComparisonOperator;
import com.amazonaws.services.dynamodb.model.Condition;
import com.amazonaws.services.dynamodb.model.ScanResult;
import com.sjsu.aws.db.QueryCriteria;
import com.sjsu.aws.db.TableOperationImpl;
import com.sjsu.aws.db.TableRow;

/**
 * 
 * @author Shweta Dattatreya
 * 
 */
public class AWSClient {
	private final static Logger LOG = Logger.getLogger(AWSClient.class);
	private static AWSClient client = null;
	protected TableOperationImpl db;
	protected static final String tableName = "LockService";
	protected static final String ID = "ID";
	protected static final String TS = "TS";
	ArrayList<LockWatcher> watchers;
	AWSClientWatchdog watchdog;
	final long timeout = 10 * 1000; // 1 minute

	/**
	 * 
	 * @throws Exception
	 */
	private AWSClient() throws Exception {
		db = new TableOperationImpl();
		watchers = new ArrayList<LockWatcher>();
		List<String> tables = db.listTables().getTableNames();
		if (tables.contains(tableName)) {
			purgeTable();
		} else {
			createTable();
		}
		watchdog = new AWSClientWatchdog(this, timeout);
		watchdog.start();
	}

	/**
	 * 
	 * @return
	 * @throws Exception
	 */
	public static synchronized AWSClient getInstance() throws Exception {
		if (client == null) {
			client = new AWSClient();
		}
		return client;
	}

	/**
	 * 
	 * @param w
	 */
	public void addWatcher(LockWatcher w) {
		watchers.add(w);
	}

	/**
	 * 
	 * @param w
	 */
	public void removeWatcher(LockWatcher w) {
		watchers.remove(w);
	}

	/**
	 * 
	 * @throws Exception
	 */
	void createTable() throws Exception {
		LOG.info("Creating Lock Service Table");
		db.createTable(tableName, ID, "S");
	}

	/**
	 * 
	 * @throws Exception
	 */
	void deleteTable() throws Exception {
		db.deleteTable(tableName);
	}

	/**
	 * 
	 * @throws Exception
	 */
	void purgeTable() throws Exception {
		LOG.info("Purging the Table");
		ScanResult result = db.queryTableWithNoCondition(tableName);
		for (Map<String, AttributeValue> item : result.getItems()) {
			for (AttributeValue v : item.values()) {
				deleteItem(v.getS());
			}
		}
	}

	/**
	 * 
	 * @param id
	 * @return
	 */
	Map<String, AttributeValue> getItem(String id) {
		Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
		item.put(ID, new AttributeValue(id));
		return item;
	}

	/**
	 * 
	 * @param id
	 * @param ts
	 * @throws Exception
	 */
	void insertItem(String id, String ts) throws Exception {
		LOG.info("Inserting items to table");
		TableRow tableRow = new TableRow();
		Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
		item.put(ID, new AttributeValue(id));
		item.put(TS, new AttributeValue(ts));
		tableRow.setItem(item);
		db.insertItem(tableName, tableRow);
	}

	/**
	 * 
	 * @param id
	 * @param ts
	 * @throws Exception
	 */
	void updateItem(String id, String ts) throws Exception {
		LOG.info("Updating the time stamp");
		TableRow tableRow = new TableRow();
		tableRow.setUpdateKey(id);
		tableRow.setUpdateColumnName(TS);
		tableRow.setUpdateColumnValue(ts);
		db.UpdatePutItem(tableName, tableRow);
	}

	/**
	 * 
	 * @param id
	 * @throws Exception
	 */
	void deleteItem(String id) throws Exception {
		LOG.info("Deleting items from the table");
		TableRow tableRow = new TableRow();
		tableRow.setDeleteKey(id);
		db.deleteItem(tableName, tableRow);
		for (LockWatcher w : watchers) {
			w.stateChanged();
		}
	}

	/**
	 * 
	 * @param id
	 * @return
	 * @throws Exception
	 */
	boolean queryItem(String id) throws Exception {
		LOG.info("Querying the Items in the Table");
		QueryCriteria criteria = new QueryCriteria();
		HashMap<String, Condition> scanFilter = new HashMap<String, Condition>();
		Condition condition = new Condition().withComparisonOperator(
				ComparisonOperator.EQ.toString()).withAttributeValueList(
				new AttributeValue().withS(id));
		scanFilter.put(ID, condition);
		criteria.setScanFilter(scanFilter);
		ScanResult scanResult = db.queryTable(tableName, criteria);
		return scanResult.getItems().contains(getItem(id));
	}

	/**
	 * 
	 * @return
	 * @throws Exception
	 */
	Map<String, String> queryTable() throws Exception {
		LOG.info("Querying the table");
		Map<String, String> result = new HashMap<String, String>();
		ScanResult scanResult = db.queryTableWithNoCondition(tableName);
		for (Map<String, AttributeValue> item : scanResult.getItems()) {
			result.put(item.get(ID).getS(), item.get(TS).getS());
		}
		return result;
	}
}
